package com.xu.cloud.common.config;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xu.cloud.common.aop.DistributeLockAspect;
import com.xu.cloud.common.aop.SingleLockAspect;
import com.xu.cloud.common.auth.AuthenticationCheckAdvisingBeanPostProcessor;
import com.xu.cloud.common.auth.AuthenticationProperties;
import com.xu.cloud.common.auth.AuthorizationCheckAdvisingBeanPostProcessor;
import com.xu.cloud.common.desensitization.*;
import com.xu.cloud.common.filter.*;
import com.xu.cloud.common.format.IMessageFormat;
import com.xu.cloud.common.format.JdkMessageFormat;
import com.xu.cloud.common.format.Slf4JMessageFormat;
import com.xu.cloud.common.idempotent.IdempotentApi;
import com.xu.cloud.common.idempotent.IdempotentIfc;
import com.xu.cloud.common.idempotent.IdempotentProperties;
import com.xu.cloud.common.idempotent.support.CaffeineIdempotent;
import com.xu.cloud.common.idempotent.support.IdempotentController;
import com.xu.cloud.common.idempotent.support.MapIdempotent;
import com.xu.cloud.common.idempotent.support.RedisIdempotent;
import com.xu.cloud.common.limit.RateLimitAdvisingBeanPostProcessor;
import com.xu.cloud.common.lock.IDistributeLock;
import com.xu.cloud.common.lock.ISingleLock;
import com.xu.cloud.common.lock.RedissonDistributeLock;
import com.xu.cloud.common.lock.ReentrantSingleLock;
import com.xu.cloud.common.process.IProcessSelector;
import com.xu.cloud.common.process.ProcessorAdvisingBeanPostProcessor;
import com.xu.cloud.common.process.ProcessorHandlerMapping;
import com.xu.cloud.common.process.XuApiScannerRegistry;
import com.xu.cloud.common.process.support.XuApiMethodAnnotatedProcessSelector;
import com.xu.cloud.common.resource.XuCloudMessageSource;
import com.xu.cloud.common.runner.XuCloudReadyBannerPrinter;
import com.xu.cloud.common.sequence.ISequence;
import com.xu.cloud.common.sequence.UUIDSequence;
import com.xu.cloud.common.trace.TraceIdContext;
import com.xu.cloud.common.trans.TransContextAdvisingBeanPostProcessor;
import com.xu.cloud.common.trans.TransLogAdvisingBeanPostProcessor;
import com.xu.cloud.common.util.ApplicationContextUtil;
import org.redisson.api.RedissonClient;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ClassUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.sql.DataSource;
import java.util.List;

/**
 * 自动装配
 *
 * @author xuguan
 * @since 2023/7/9
 */
@Configuration
public class XuCloudAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public XuCloudMessageSource xuCloudMessageSource(MessageSource messageSource) {
        return new XuCloudMessageSource(messageSource);
    }

    @Bean
    @ConditionalOnMissingBean
    public IMessageFormat messageFormat() {
        if (ClassUtils.isPresent("org.slf4j.helpers.MessageFormatter",
                ClassUtils.getDefaultClassLoader())) {
            return new Slf4JMessageFormat();
        }
        return new JdkMessageFormat();
    }

    @Bean
    @ConditionalOnMissingBean
    public ISequence sequence() {
        return new UUIDSequence();
    }

    @Bean
    @ConditionalOnMissingBean(CorsFilter.class)
    public CorsFilter corsFilter() {
        CorsConfiguration configuration = new CorsConfiguration();
        // springboot2.4.0以上版本, 需使用addAllowedOriginPattern("*")
        configuration.addAllowedOrigin("*");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        // 允许携带cookie
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

        return new OrderedCorsFilter(source);
    }

    @Bean
    @ConditionalOnMissingBean
    public TraceIdContextFilter traceIdContextFilter(ObjectProvider<TraceIdContext> traceIdContextProvider) {
        return new TraceIdContextFilter(traceIdContextProvider);
    }

    @Bean
    @ConditionalOnMissingBean
    public HttpHeadersSettingFilter headersSettingFilter() {
        return new HttpHeadersSettingFilter();
    }

    @Bean
    @ConditionalOnMissingBean
    public ApiContextFilter httpRequestContextFilter() {
        return new ApiContextFilter();
    }

    @Bean
    @ConditionalOnMissingBean
    public ApiLoggingFilter httpRequestLoggingFilter() {
        return new ApiLoggingFilter();
    }

    @ConditionalOnBean({RedisTemplate.class, DataSource.class})
    @ConditionalOnClass({RedisTemplate.class, DataSource.class, BaseMapper.class})
    @ConditionalOnProperty(prefix = "request.blocking", name = "enable", havingValue = "true")
    @Configuration
    @Import(HttpRequestBlockingFilter.class)
    public static class HttpRequestBlockingFilterConfiguration {
    }

    @Bean
    @ConditionalOnMissingBean
    public ApplicationContextUtil applicationContextUtil() {
        return new ApplicationContextUtil();
    }

    @Bean
    @ConditionalOnMissingBean
    public XuApiScannerRegistry xuApiScannerRegistry() {
        return new XuApiScannerRegistry();
    }

    @Bean
    @ConditionalOnMissingBean(name = {"processorHandlerMapping"})
    public ProcessorHandlerMapping processorHandlerMapping(ObjectProvider<RequestMappingHandlerMapping> requestMappingHandlerMappingObjectProvider) {
        return new ProcessorHandlerMapping(requestMappingHandlerMappingObjectProvider);
    }

    @Bean
    @ConditionalOnMissingBean
    public IProcessSelector processorSelector() {
        return new XuApiMethodAnnotatedProcessSelector();
    }

    @Bean
    @ConditionalOnMissingBean(name = {"processorAdvisingBeanPostProcessor"})
    public ProcessorAdvisingBeanPostProcessor processorAdvisingBeanPostProcessor() {
        return new ProcessorAdvisingBeanPostProcessor();
    }

    // @Bean
    // @ConditionalOnMissingBean(name = {"transContext", "transContextAspect"})
    // public TransContextAspect transContextAspect() {
    //     return new TransContextAspect();
    // }

    @Bean
    @ConditionalOnMissingBean(name = {"transContextAdvisingBeanPostProcessor"})
    public TransContextAdvisingBeanPostProcessor transContextAdvisingBeanPostProcessor() {
        return new TransContextAdvisingBeanPostProcessor();
    }

    // @Bean
    // @ConditionalOnMissingBean(name = {"transLog", "transLogAspect"})
    // public TransLogAspect transLogAspect() {
    //     return new TransLogAspect();
    // }

    @Bean
    @ConditionalOnMissingBean(name = {"transLogAdvisingBeanPostProcessor"})
    public TransLogAdvisingBeanPostProcessor transLogAdvisingBeanPostProcessor() {
        return new TransLogAdvisingBeanPostProcessor();
    }

    // @Bean
    // @ConditionalOnMissingBean(name = {"rateLimit", "rateLimitAspect"})
    // public RateLimitAspect rateLimitAspect() {
    //     return new RateLimitAspect();
    // }

    @Bean
    @ConditionalOnMissingBean(name = {"rateLimitAdvisingBeanPostProcessor"})
    public RateLimitAdvisingBeanPostProcessor rateLimitAdvisingBeanPostProcessor() {
        return new RateLimitAdvisingBeanPostProcessor();
    }

    @Configuration
    @EnableConfigurationProperties(AuthenticationProperties.class)
    public static class AuthConfig {
        @Bean
        @ConditionalOnProperty(prefix = "xu.cloud.authentication", name = "enable", havingValue = "true", matchIfMissing = true)
        @ConditionalOnMissingBean(name = {"authenticationCheckAdvisingBeanPostProcessor"})
        public AuthenticationCheckAdvisingBeanPostProcessor authenticationCheckAdvisingBeanPostProcessor(AuthenticationProperties properties) {
            return new AuthenticationCheckAdvisingBeanPostProcessor(properties);
        }

        @Bean
        @ConditionalOnProperty(prefix = "xu.cloud.authorization", name = "enable", havingValue = "true", matchIfMissing = true)
        @ConditionalOnMissingBean(name = {"authorizationCheckAdvisingBeanPostProcessor"})
        public AuthorizationCheckAdvisingBeanPostProcessor authorizationCheckAdvisingBeanPostProcessor() {
            return new AuthorizationCheckAdvisingBeanPostProcessor();
        }
    }

    @Configuration
    public static class DesensitizationConfig {
        @Bean
        @ConditionalOnMissingBean
        public IDeSensitizationRuleFactory desensitizationRuleFactory() {
            return new DefaultDesensitizationRuleFactory();
        }

        @Bean
        @ConditionalOnMissingBean
        public DesensitizationRuleCustomizers desensitizationRuleCustomizers(IDeSensitizationRuleFactory deSensitizationRuleFactory,
                                                                             List<IDesensitizationRuleCustomizer> customizers) {
            final DesensitizationRuleCustomizers ruleCustomizers = new DesensitizationRuleCustomizers(customizers);
            ruleCustomizers.customize(deSensitizationRuleFactory);
            return ruleCustomizers;
        }

        @Bean
        @ConditionalOnMissingBean
        public DesensitizationHandler desensitizationHandler(IDeSensitizationRuleFactory desensitizationRuleFactory) {
            return new DesensitizationHandler(desensitizationRuleFactory);
        }
    }

    @Configuration
    @ConditionalOnProperty(prefix = "xu.cloud.idempotent", name = "enable", havingValue = "true", matchIfMissing = true)
    @EnableConfigurationProperties(IdempotentProperties.class)
    public static class IdempotentConfig {
        private final ISequence sequence;
        private final IdempotentProperties properties;
        private final ApplicationContext ctx;

        public IdempotentConfig(ISequence sequence,
                                IdempotentProperties properties,
                                ApplicationContext ctx) {
            this.sequence = sequence;
            this.properties = properties;
            this.ctx = ctx;
        }

        @Bean
        @ConditionalOnMissingBean
        public IdempotentIfc idempotentIfc() {
            final ClassLoader cl = this.getClass().getClassLoader();
            if (ClassUtils.isPresent("org.springframework.data.redis.core.StringRedisTemplate", cl)) {
                try {
                    final StringRedisTemplate stringRedisTemplate =
                            BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, StringRedisTemplate.class, true, false);
                    return new RedisIdempotent(stringRedisTemplate, sequence, properties);
                } catch (BeansException e) {
                    // ignore
                }
            }
            if (ClassUtils.isPresent("com.github.benmanes.caffeine.cache.Caffeine", cl)) {
                try {
                    return new CaffeineIdempotent(sequence, properties);
                } catch (BeansException e) {
                    // ignore
                }
            }
            return new MapIdempotent(sequence, properties);
        }

        @Bean
        @ConditionalOnMissingBean
        public IdempotentApi idempotentApi(IdempotentIfc idempotentIfc) {
            return new IdempotentController(idempotentIfc);
        }
    }

    @Configuration
    public static class LockConfig {

        @Bean
        @ConditionalOnMissingBean
        public ISingleLock singleLock() {
            return new ReentrantSingleLock();
        }

        @Configuration
        @AutoConfigureAfter(RedissonAutoConfiguration.class)
        @ConditionalOnClass(value = {org.aspectj.weaver.Advice.class, org.redisson.Redisson.class})
        public static class RedissonLockConfig {
            @Bean
            @ConditionalOnMissingBean
            public IDistributeLock distributeLock(RedissonClient redissonClient) {
                return new RedissonDistributeLock(redissonClient);
            }

            @Bean
            @ConditionalOnMissingBean(name = "singleLockAspect")
            public SingleLockAspect singleLockAspect(ISingleLock singleLock) {
                return new SingleLockAspect(singleLock);
            }

            @Bean
            @ConditionalOnMissingBean(name = "distributeLockAspect")
            public DistributeLockAspect distributeLockAspect(IDistributeLock distributeLock) {
                return new DistributeLockAspect(distributeLock);
            }
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public XuCloudReadyBannerPrinter xuCloudReadyBannerPrinter() {
        return new XuCloudReadyBannerPrinter();
    }

}
